#include "epoll_server.h"

#define MAXSIZE 2000

/* 创建一个错误404的http页面*/
void send_error(int cfd, int status, char *title, char *text){
    char buf[4096] = {0};

    sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);
    sprintf(buf+strlen(buf), "Context-Type:%s\r\n", "text/html");
    sprintf(buf+strlen(buf), "Connection: close\r\n");
    send(cfd, buf, strlen(buf), 0);
    send(cfd, "\r\n", 2, 0);

    memset(buf, 0, sizeof(buf));

    sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
    sprintf(buf+strlen(buf), "<body bgcolor=\"#cc99cc\"><h4 align=\"center\">%d %s</h4>\n", status, title);
    sprintf(buf+strlen(buf), "%s\n", text);
    sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");
    send(cfd, buf, sizeof(buf),0);

    return;
}

// 设置监听节点，并将其加入到监听树上
int init_listen_fd(int port, int epfd){
    int ret, i;
    struct sockaddr_in serv_addr;

    // 创建监听的套接字 lfd
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket error");
        exit(1);
    }

    // 创建服务处地址结构IP+Port
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 给lfd绑定地址结构
    ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1){
        perror("bind error");
        exit(1);
    }

    // 设置监听上限
    ret = listen(lfd, MAXSIZE);
    if(ret == -1){
        perror("listen error");
        exit(1);
    }

    // 将lfd添加到epoll树上
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if(ret == -1){
        perror("epoll_ctl add lfd error");
        exit(1);
    }

    return lfd;
}


// 当有客户端连接到服务器时，创建cilt_addr socket，并将其添加到监听树中
void do_accept(int lfd, int epfd){
    struct sockaddr_in clit_addr;
    socklen_t clit_addr_len = sizeof(clit_addr);

    int cfd = accept(lfd, (struct sockaddr*)&clit_addr, &clit_addr_len);
    if(cfd == -1){
        perror("accept error");
        exit(1);
    }

    // 打印客户端IP+Port
    char client_ip[64];
    printf("New Client IP: %s, Port: %d, cfd = %d\n", 
        inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(clit_addr.sin_port), cfd);

    // 设置 cfd 为非阻塞
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd, F_SETFL, flag);

    // 将新节点cfd挂载到epoll监听树上
    struct epoll_event ev;
    ev.data.fd = cfd;

    // 设置为边沿非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if(ret == -1){
        perror("epoll_ctl add cfd error");
        exit(1);
    }
}

// 封装断开连接的函数
void disconnect(int cfd, int epfd){
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if(ret == -1){
        perror("epoll_ctl_del error");
        exit(1);
    }
    close(cfd);
}

// 处理http回发应答协议
/* cfd: 客户端的fd
** no : 错误号
** disp: 错误描述
** type: 回发文件类型
** len : 文件长度（可以不设定，如果设定，则必须保证其正确）
*/
void send_respond(int cfd, int no, char *disp, char *type, int len){
    printf("send_responed function!\n");
    int ret;
    char buf[1024];

    sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
    sprintf(buf+strlen(buf), "%s\r\n", type);
    sprintf(buf+strlen(buf), "Content_Length:%d\r\n", len);
    printf("%s\n", buf);
    // 改成sizeof就无法正确输出
    ret = send(cfd, buf, strlen(buf), 0);
    
    //printf("strlen(buf) = %d\n", strlen(buf));
    //printf("sizeof(buf) = %d\n", sizeof(buf));
    if(ret == -1){
        perror("send_respond-send1 error");
        return;
    }
    ret = send(cfd, "\r\n", 2, 0);
    if(ret == -1){
        perror("send_respond-send2 error");
        return;
    }
}

// 发送服务器本地文件给浏览器
void send_file(int cfd, const char *file){
    printf("send_file\n");
    // fd为打开的服务器本地文件，cfd为能访问客户端的socket
    int n=0, fd, ret;
    char buf[4096] = {0};

    fd = open(file, O_RDONLY);
    if(fd == -1){
        // void send_error(int cfd, int status, char *title, char *text);
        send_error(cfd, 404, "File Not Find", "FILE NOT FOUND 404");    // 发送错误信息页面
        perror("send_file:open error");
        close(cfd);
        return;
    }
    
    while( (n = read(fd, buf, sizeof(buf))) > 0 ){
        if(n == -1){
            perror("send_file:read error");
            break;
        }

        send(STDOUT_FILENO, buf, n, 0);
        ret = send(cfd, buf, n, 0);
        if(ret == -1){
            //fprintf(stderr, "errno=%d\n", errno);
            if(errno == EAGAIN) continue;
            else if(errno == EINTR) continue;
            else{
                perror("send_file:send error");
                break;
            }      
        }
        
        memset(buf, '\0', sizeof(buf));
        
    }
    //if(n == 0) printf("send successful\n");

    close(fd);

}

// 处理http请求，判断文件是否存在，回发数据
void http_request(int cfd, const char *file){
    printf("http_request\n");

    struct stat sbuf;
    int ret;

    // 需要首先判断是否为目录：：：：如果没有指定访问的资源，则默认资源目录中的内容
    if(strcmp(file, "") == 0){
        // file的值为当前的资源目录的位置
        file = "./";
    }

    // 判断文件是否存在
    ret = stat(file, &sbuf);
    if(ret != 0){
        // 回发浏览器404错误页面
        send_error(cfd, 404, "Send ERROR", "FILE NOT FOUND 404");
        perror("http_request: stat");
        return;
    }


    // 判断是否为普通文件还是目录文件
    if(S_ISDIR(sbuf.st_mode)){      // 此时为目录文件
        // 发送头信息
        //printf("This is a dir!!!\n");
        send_respond(cfd, 200, "OK", (char *)get_file_type(".html"), -1);
        send_dir(cfd, file);
    }else if(S_ISREG(sbuf.st_mode)){  // 此时为普通文件
        //printf("This is a file!!!\n");
        char buf[96] = {0};

        sprintf(buf, get_file_type(file));
        printf("%s\n", buf);

        // 回发http应答协议
        send_respond(cfd, 200, "OK", (char *)get_file_type(file), sbuf.st_size);
        
        // 回发给客户端请求数据内容 
        send_file(cfd, file);
    }
}

// 解码操作：---》将unicdoe码重新转换为汉字
void decode_str(char *to, char *from){
    for(; *from != '\0'; ++to, ++from){
        if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])){
            *to = hexit(from[1])*16 + hexit(from[2]);
            from += 2;
        } else {
            *to = *from;
        }
    }
    *to = '\0';
}

// 十六进制转化为10进制
int hexit(char c){
    if(c >= '0' && c <= '9')
        return c - '0';
    else if( c >= 'a' && c <=  'f')
        return c - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    return 0;
}

/* 
 * 这里的内容是处理%20之类的东西！ 是解码的过程
 * %20 URL编码中的' ' （space)
 * %21 ' !' %22 '"' %23 '#' %24 '$'
 * %25 '%' %26 '^' %27 '&' .....
 * 相关知识html中的' '(space)是&nbsp
 */ 
void encode_str(char *to, int tosizes, const char *from){
    int tolen;

    for(tolen = 0; *from != '\0' && tolen + 4 < tosizes; ++from){
        if(isalnum(*from) || strchr("/_.-~", *from) != (char*)0){
            *to = *from;
            ++to;
            ++tolen;
        }else{
            sprintf(to, "%%%02x", (int)*from&0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}


// 发送目录文件
void send_dir(int cfd, const char *dirname){
    int i,ret;

    // 拼一个html页面<table></table>
    char buf[4096] = {0};

    sprintf(buf, "<html><head><title>目录名： %s</title></head>", dirname);
    sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);

    char enstr[1024] = {0};
    char path[1024] = {0};

    // 目录项二级指针
    struct dirent** ptr;
    int num = scandir(dirname, &ptr, NULL, alphasort);
    
    // 遍历
    for(i = 0; i < num; ++i){
        char* name = ptr[i]->d_name;

        // 拼接文件的完整路径
        sprintf(path, "%s/%s", dirname, name);
        printf("path = %s ====================\n", path);
        struct stat st;
        stat(path, &st);

        encode_str(enstr, sizeof(enstr), name);

        // 如果是文件
        if(S_ISREG(st.st_mode)){
            sprintf(buf+strlen(buf), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        }else if(S_ISDIR(st.st_mode)){
            // 如果是目录，则显示目录
            sprintf(buf+strlen(buf), "<tr><td><a href=\"%s/\">%s/</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        }

        ret = send(cfd, buf, strlen(buf), 0);
        if(ret == -1){
            if(errno == EAGAIN){
                perror("send error and BLOCKING...");
                continue;
            }else if(errno = EINTR){
                perror("send error and Interrupt....");
                continue;
            }else{
                perror("send error");
                return;
            }
        }

        memset(buf, 0, sizeof(buf));
    }

    // 进行字符串的拼接
    sprintf(buf+strlen(buf), "</table></body></html>");
    send(cfd, buf, strlen(buf), 0);

    printf("dir message send ok !!! \n");

#if 0
    // 打开目录
    DIR *dir = opendir(dirname);
    if(dir == NULL){
        perror("opendir error");
        exit(1);
    }

    // 读目录
    struct dirent* ptr = NULL;
    while((ptr = readdir(dir)) != NULL){
        char *name = ptr->d_name;
    }
    closedir(dir);
#endif

}

void do_read(int cfd, int epfd){
    //  读取一行http协议，拆分，获取 ： get 文件名 协议号
    char line[1024] = {0};
    int len = get_line(cfd, line, sizeof(line));        // 读http请求协议首行 GET /hello.c HTTP/1.1
    //printf("%s\n", line);
    if(len == 0){
        printf("服务器检测到客户端关闭...\n");
        disconnect(cfd, epfd); 
    }else if(len == -1){
        perror("do_read: geteline error");
        return;
    }else{
        // 进行字符串分割，读取首行中的参数, 可以使用sscanf函数
        char method[16], path[256], protocol[16];
        sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);

        // 转码-->将不能识别的中文乱码-->中文
        // 解码 %23 %34 %5f
        decode_str(path, path);

        printf("\n----------------请求头---------\n");
        printf("method = %s, path = %s, protocol = %s", method, path, protocol);
        printf("---------------请求内容-------------\n");
        while(1){
            char buf[1024] = {0};
            len = get_line(cfd, buf, sizeof(buf));
            if(len <= 0) break;
            //printf("%s", buf);
        }

        // 判断是否为GET头部
        if(strncasecmp(method, "GET", 3) == 0){
            // path+1为客户端要访问的文件名, 处理http请求
            // char *file = path + 1;
            http_request(cfd, path + 1);
            // 关闭套接字,cfd从epoll上del
            disconnect(cfd, epfd);
        }
    }
}

void epoll_run(int port){
    int i = 0;
    struct epoll_event all_event[MAXSIZE];

    // 创建一个epoll监听树根
    int epfd = epoll_create(MAXSIZE);
    if(epfd == -1){
        perror("epoll_run: epoll_create error");
        exit(1);
    }

    // 创建lfd，并将其添加到监听树上
    int lfd = init_listen_fd(port, epfd);
    
    // 监听节点对应事件
    while(1){
        int ret = epoll_wait(epfd, all_event, MAXSIZE, -1);
        if(ret == -1){
            perror("epoll_run: epoll_wait error");
            exit(1);
        }

        for(i = 0; i<ret; ++i){
            // 只处理读事件，其他事件默认不处理
            struct epoll_event *pev = all_event;

            // 判断是否为读事件
            if(!(pev->events & EPOLLIN)){
                continue;
            }

            // 如果data==lfd，表明有连接请求
            if(pev->data.fd == lfd){
                do_accept(lfd, epfd);
            }else{
                do_read(pev->data.fd, epfd);
            }
        }
    }
}

